-- 2022-8-18

--[[ 20.4 表相关的元方法

    算术运算符 位运算符和关系运算符的元方法都定义了各种错误情况的行为 但它们都没有改变语言的正常行为
    Lua语言还提供了一种改变表在两种正常情况下的行为的方式 即访问和修改表中不存在的字段
]]

---[[ 20.4.1 __index元方法

    -- 正如我们此前所看到的 但访问一个表中不存在的字段时会得到nil 这是正确的 但不是完整的真相
    -- 实际上 这些访问会引发解释器查找一个名为__index的元方法
    -- 如果没有这个元方法 那么像一般情况下一样 结果就是nil 否则 则由这个元方法来提供最终结果

    -- 下面介绍一个关于继承的原型示例 假设我们要创建几个表来描述窗口 每个表中必须描述窗口的一些参数 例如位置 大小及主题颜色等
    -- 所有的这些参数都有默认值 因此我们希望在创建窗口对象时只需要给出那些不同于默认值的参数即可
    -- 第一种方法是使用一个构造器来填充不存在的字段 第二种方法是让新窗口从一个原型窗口继承所有不存在的字段 首先 我们声明一个原型
    -- 创建具有默认值的原型
    prototype = {x=0,y=0,width=100,height=100}
    -- 然后 声明一个构造函数 让构造函数创建共享同一个元表的新窗口:
    local mt = {} -- 创建一个元表
    -- 声明一个构造函数
    function new(o)
        setmetatable(o,mt)
        return o
    end
    -- 现在 我们来定义元方法__index
    mt,__index = function (_,key)
        return prototype[key]
    end
    -- 在这段代码后 创建一个新窗口 并查询一个创建时没有指定的字段
    w = new{x=10,y=20}
    print(w.width)

    -- Lua语言会发现w中没有对应的字段"width" 但却有一个带有__index元方法的元表
    -- 因此 Lua语言会以w(表)和"width"(不存在的键)为参数来调用这个元方法 元方法随后会用这个键来检索原型并返回结果

    -- 在Lua语言中 使用元方法__index来实现继承是很普遍的方法
    -- 虽然被叫做方法 但元方法__index不一定必须是一个函数 它可以是一个表
    -- 当元方法是一个函数时 Lua语言会以表和不存在的键为参数调用该函数 正如我们刚刚所看到的 当元方法是一个表时 Lua语言就访问这个表
    -- 因此 在我们此前的示例中 可以把__index简单地声明为如下样式:
    mt.__index = prototype

    -- 这样 当Lua语言查找元表的__index字段时 会发现字段的值是表prototype
    -- 因此 Lua语言就会在这个表中继续查找 即等价地执行prototype["width"] 并得到预期的结果

    -- 将一个表用作__index元方法为实现单继承提供了一种简单快捷的方法
    -- 虽然将函数用作元方法开销更昂贵 但函数却更加灵活:我们可以通过函数来实现多继承 缓存及其他一些变体
    -- 我们将会在第21章中学习面向对象编程时讨论这些形式的继承

    -- 如果我们希望在访问一个表时不调用__index元方法 那么可以使用函数rawget
    -- 调用rawget(t,i)会对表t进行原始(raw)的访问 即在不考虑元表的情况下对表进行简单的访问
    -- 进行一次原始访问并不会加快代码的执行(一次函数调用的开销就会抹杀用户所做的这些努力) 但是 我们后续会看到 有时确实会用到原始访问
--]]